// 
//  Author:
//    anton 
// 
//  Copyright (c) 2010, anton
// 
//  All rights reserved.
// 
//  Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in
//       the documentation and/or other materials provided with the distribution.
//     * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
// 
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
using System;
using System.IO;
using System.Collections.Generic;

using OpenMetaverse;
namespace OpenSimCache
{
	class MainClass
	{
		public static string exportPath = "/home/anton/OpenSim/CacheDump";
		public static string jp2ExportPath = exportPath + Path.DirectorySeparatorChar + "jp2";
		public static string tgaExportPath = exportPath + Path.DirectorySeparatorChar + "tga";
		public static string oggExportPath = exportPath + Path.DirectorySeparatorChar + "ogg";
		static string rootPath = "/home/anton/.imprudence/cache";

		static string dataFileNameBase = "data.db2.x.";
		static string indexFileNameBase= "index.db2.x.";
		
		struct CacheIndexItem
		{
			public Int32 Offset;
			public Int16 Magic;
			public Int16 Flags;
			public DateTime Time;
			public UUID  ID;
			public Int16 Type;
			public Int32 Size;
			
			public CacheIndexItem(BinaryReader reader)
			{
				Offset = reader.ReadInt32();
				Magic = reader.ReadInt16();
				Flags = reader.ReadInt16();
				Time = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(reader.ReadUInt32());
				ID = new UUID(reader.ReadBytes(16), 0);
				Type = reader.ReadInt16();
				Size = reader.ReadInt32();
			}
		}
		
		static void HandleIndexItem(IarFile iar, CacheIndexItem indexItem, byte[] data)
		{
			string fileName;

			switch(indexItem.Type)
			{
				case 0x0000:
					fileName = string.Format("{0}.j2c", indexItem.ID.ToString());
					break;
				case 0x0001:
					fileName = string.Format("{0}.ogg", indexItem.ID.ToString());
					break;
				case 0x0002:
					fileName = string.Format("{0}.callcard", indexItem.ID.ToString());
					break;
				case 0x0003:
					fileName = string.Format("{0}.landmark", indexItem.ID.ToString());
					break;
				case 0x0004:
					fileName = string.Format("{0}.lsl", indexItem.ID.ToString());
					break;
				case 0x0005:
					fileName = string.Format("{0}" + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Clothing], indexItem.ID.ToString());
					break;
				case 0x0006:
					fileName = string.Format("{0}.object", indexItem.ID.ToString());
					break;
				case 0x0007:
					fileName = string.Format("{0}.notecard", indexItem.ID.ToString());
					break;
				case 0x0008:
					fileName = string.Format("{0}.category", indexItem.ID.ToString());
					break;
				case 0x0009:
					fileName = string.Format("{0}.category", indexItem.ID.ToString());
					break;
				case 0x000a:
					fileName = string.Format("{0}.lsl", indexItem.ID.ToString());
					break;
				case 0x000b:
					fileName = string.Format("{0}.lso", indexItem.ID.ToString());
					break;
				case 0x000c:
					fileName = string.Format("{0}.tga", indexItem.ID.ToString());
					break;
				case 0x000d:
					fileName = string.Format("{0}" + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Bodypart], indexItem.ID.ToString());
					break;
				case 0x000e:
					fileName = string.Format("{0}.trash", indexItem.ID.ToString());
					break;
				case 0x000f:
					fileName = string.Format("{0}.snapshot", indexItem.ID.ToString());
					break;
				case 0x0010:
					fileName = string.Format("{0}.lstndfnd", indexItem.ID.ToString());
					break;
				case 0x0011:
					fileName = string.Format("{0}.wav", indexItem.ID.ToString());
					break;
				case 0x0012:
					fileName = string.Format("{0}.tga", indexItem.ID.ToString());
					break;
				case 0x0013:
					fileName = string.Format("{0}.jpg", indexItem.ID.ToString());
					break;
				case 0x0014:
					fileName = string.Format("{0}" + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Animation], indexItem.ID.ToString());
					break;
				case 0x0015:
					fileName = string.Format("{0}"  + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.Gesture], indexItem.ID.ToString());
					break;
				case 0x0016:
					fileName = string.Format("{0}.simstate", indexItem.ID.ToString());
					break;
				default:
					fileName = string.Format("---------------{0}.tmp", indexItem.ID.ToString());
					break;
			}
			if(indexItem.Type == 0x0001)
			{
				FileStream dumpedCache = new FileStream(Path.Combine(oggExportPath, fileName), FileMode.Create, FileAccess.Write);
				dumpedCache.Write(data, 0, data.Length);
				dumpedCache.Flush();
				dumpedCache.Close();
			}
			else if(indexItem.Type == 0x0014)
			{
				iar.WriteAnimation(fileName, indexItem.ID, data);
			}
			else if(indexItem.Type == 0x0005)
			{
				iar.WriteClothing(fileName, indexItem.ID, data);
			}
			else if(indexItem.Type == 0x000d)
			{
				iar.WriteBodyPart(fileName, indexItem.ID, data);
			}
			else if(indexItem.Type == 0x0015)
			{
				iar.WriteGesture(fileName, indexItem.ID, data);
			}
			else if(indexItem.Type != 0x000)
			{
				FileStream dumpedCache = new FileStream(Path.Combine(exportPath, fileName), FileMode.Create, FileAccess.Write);
				dumpedCache.Write(data, 0, data.Length);
				dumpedCache.Flush();
				dumpedCache.Close();
			}
		}
		
		public static void Main(string[] args)
		{
			if(!Directory.Exists(exportPath))
			   Directory.CreateDirectory(exportPath);
			   
			if(!Directory.Exists(jp2ExportPath))
			   Directory.CreateDirectory(jp2ExportPath);

			if(!Directory.Exists(tgaExportPath))
			   Directory.CreateDirectory(tgaExportPath);

			if(!Directory.Exists(oggExportPath))
			   Directory.CreateDirectory(oggExportPath);

			TextureCache texCache = new TextureCache(rootPath);
			int errorCount = 0;
			
			foreach(UUID id in texCache.ImageIDs())
			{
				byte[] image = texCache.GetImage(id);
				if(image != null)
				{
					File.WriteAllBytes(Path.Combine(jp2ExportPath, id.ToString())+".jp2", image );
					try
					{
						OpenMetaverse.Imaging.OpenJPEG.J2KLayerInfo[] layerInfo;
						int components = -1;
						OpenMetaverse.Imaging.OpenJPEG.DecodeLayerBoundaries(image, out layerInfo, out components);
						
						if(components > 0)
						{
							OpenMetaverse.Imaging.ManagedImage tgaImage;
							if(OpenMetaverse.Imaging.OpenJPEG.DecodeToImage(image, out tgaImage))
							{
								/*
								 * bool emptyAlpha = true;
								for(Int64 idx=0; idx<tgaImage.Height*tgaImage.Width; idx++)
									if(tgaImage.Alpha[idx] == 0xff)
										emptyAlpha = false;
								
								if(emptyAlpha)
								{
									Console.WriteLine("Empty alpha: {0}", id.ToString());
									for(Int64 idx=0; idx<tgaImage.Height*tgaImage.Width; idx++)
										tgaImage.Alpha[idx] = 0x0;
								}
								*/
								byte[] rawTgaImage = tgaImage.ExportTGA();
								File.WriteAllBytes(Path.Combine(tgaExportPath, id.ToString())+".tga", rawTgaImage );
								//File.Delete(Path.Combine(jp2ExportPath, id.ToString())+".jp2");
							}
						}
						else
						{
							errorCount++;
						}
					} 
					catch(Exception e)
					{
						Console.WriteLine("-------" + e.Message + " " + id);
						break;
					}
				}
			}
			if(errorCount != 0)
				Console.WriteLine("JP2->TGA Conversion error count=" + errorCount);

			// find the name of the index and data files (the numeric component at the tail
			string[] fileList = Directory.GetFiles(rootPath, indexFileNameBase + "*");
			if(fileList.Length != 1)
			{
				Console.Error.WriteLine("Unable to determine the filenames of cache and index");
				return;
			}
			string numericComponent = fileList[0].Substring(fileList[0].LastIndexOf(".")+1);
			string indexFileName = indexFileNameBase + numericComponent;
			string dataFileName = dataFileNameBase + numericComponent;
			
			IarFile iar = new IarFile();
			using(FileStream indexStream = new FileStream(Path.Combine(rootPath, indexFileName), FileMode.Open, FileAccess.Read))
			{
				using(BinaryReader indexReader = new BinaryReader(indexStream))
				{
					using(FileStream dataStream = new FileStream(Path.Combine(rootPath, dataFileName), FileMode.Open, FileAccess.Read))
					{
						while(indexStream.Position < indexStream.Length)
						{
							CacheIndexItem indexItem = new CacheIndexItem(indexReader);
							if(indexItem.Offset > 0 && indexItem.Size > 0)
							{
								byte[] data = new byte[indexItem.Size];
								dataStream.Seek(indexItem.Offset, SeekOrigin.Begin);
								dataStream.Read(data, 0, indexItem.Size);
								HandleIndexItem(iar, indexItem, data);
							}
						}
					}
				}
			}
			File.WriteAllBytes(exportPath + "/.."+ Path.DirectorySeparatorChar +"Import.iar.tgz", iar.PackageArchive());
		}
	}
}
